package com.scriptlib;

import android.graphics.Bitmap;
import android.graphics.Point;
import android.media.Image;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GraphicHelper {
    private int Width = -1;
    private int Height = -1;
    private int RowStride = -1;
    private int PxFormat = -1;
    private byte[] ScreenData;

    private short[] RedList;
    private final int[] Starts = new int[256];
    private final int[] Steps = new int[256];

    private final AJFunc AjFunc;

    /**
     * 静态构造函数
     *
     * @param obj aj_runtime
     * @return GraphicHelper
     */
    public static GraphicHelper Create(Object obj) {
        GraphicHelper helper = new GraphicHelper(obj);
        if (helper.ReInitialize()) {
            return helper;
        }
        return null;
    }

    private GraphicHelper(Object obj) {
        this.AjFunc = new AJFunc(obj);
    }

    /**
     * 初始化环境
     */
    public boolean ReInitialize() {
        Image image = AjFunc.AcquireLatestImage();
        if (image == null) {
            return false;
        }
        ByteBuffer byteBuf = image.getPlanes()[0].getBuffer();
        Width = image.getWidth();
        Height = image.getHeight();
        RowStride = image.getPlanes()[0].getRowStride();
        PxFormat = RowStride / Width;
        ScreenData = new byte[RowStride * Height];
        byteBuf.position(0);
        byteBuf.get(ScreenData, 0, RowStride * Height);
        RedList = new short[Width * Height * 2];
        image.close();
        return true;
    }

    /**
     * 获取版本号
     */
    static public String Version() {
        return AJFunc.Version();
    }

    /**
     * 获取插件信息(开源地址)
     */
    static public String GetInfo() {
        return AJFunc.GetInfo();
    }

    /**
     * 获取截图数据到数组
     *
     * @param sign 是否使用多点找色相关
     */
    public boolean KeepScreen(boolean sign) {
        if (GetLastFrame()) {
            if (sign) {
                GetRedList();
            }
            return true;
        } else {
            return false;
        }
    }

    public boolean KeepScreen(Bitmap bitmap, boolean sign) {
        if (GetDataFromBitmap(bitmap)) {
            if (sign) {
                GetRedList();
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取最新帧到数组
     */
    private boolean GetLastFrame() {
        Image image = AjFunc.AcquireLatestImage();
        if (image == null) {
            return false;
        }
        ByteBuffer byteBuf = image.getPlanes()[0].getBuffer();
        byteBuf.position(0);
        byteBuf.get(ScreenData, 0, RowStride * Height);
        image.close();
        return true;
    }

    /**
     * 从图片获取数据到数组
     */
    private boolean GetDataFromBitmap(Bitmap bitmap) {
        Width = bitmap.getWidth();
        Height = bitmap.getHeight();
        int len = bitmap.getByteCount();
        RowStride = len / Height;
        PxFormat = RowStride / Width;
        ScreenData = new byte[len];

        ByteBuffer byteBuf = ByteBuffer.allocate(len);
        bitmap.copyPixelsToBuffer(byteBuf);
        byteBuf.position(0);
        byteBuf.get(ScreenData, 0, len);
        return true;
    }

    /**
     * 获取所有r值对应的坐标,找色效率更快
     */
    public void GetRedList() {
        int[] lens = new int[256];
        for (int i = 0; i < Height; i++) {
            int location = RowStride * i;
            for (int j = 0; j < Width; j++, location += PxFormat) {
                int k = ScreenData[location] & 0xff;
                lens[k]++;
            }
        }

        int start = 0;
        for (int i = 0; i < 256; i++) {
            Starts[i] = start;
            Steps[i] = start;
            start += lens[i] * 2;
        }

        for (int i = 0; i < Height; i++) {
            int location = RowStride * i;
            for (int j = 0; j < Width; j++, location += PxFormat) {
                int k = ScreenData[location] & 0xff;
                RedList[Steps[k]] = (short) j;
                RedList[Steps[k] + 1] = (short) i;
                Steps[k] += 2;
            }
        }
    }

    /**
     * 四舍六入五成双
     */
    public int Round(double num) {
        int local = (int) ((num - (int) num) * 100);
        if (local / 10 >= 6)
            return (int) num + 1;
        if (local / 10 <= 4)
            return (int) num;
        if (local % 10 == 0) {
            if ((int) num % 2 == 0) {
                return (int) num;
            }
        }
        return (int) num + 1;
    }

    /**
     * 比Math类库abs()方法性能更高的取绝对值的方法 from https://blog.csdn.net/qq_15071263/article/details/77142001
     */
    public int Abs(int num) {
        return (num ^ (num >> 31)) - (num >> 31);
    }

    /**
     * 暴露图像数据
     */
    public byte[] GetScreenData() {
        return ScreenData;
    }

    /**
     * 获取bitmap
     */
    public Bitmap GetBitmap() {
        return GetBitmap(0, 0, Width - 1, Height - 1);
    }

    public Bitmap GetBitmap(int sx, int sy, int ex, int ey) {
        sx = Math.max(sx, 0);
        sy = Math.max(sy, 0);
        ex = Math.min(ex, Width - 1);
        ey = Math.min(ey, Height - 1);
        int width = ex - sx + 1;
        int height = ey - sy + 1;
        byte[] data = new byte[width * height * 4];
        int site = 0;
        for (int i = sy; i <= ey; i++) {
            int location = sx * PxFormat + i * RowStride;
            for (int j = sx; j <= ex; j++) {
                data[site] = ScreenData[location];
                data[site + 1] = ScreenData[location + 1];
                data[site + 2] = ScreenData[location + 2];
                data[site + 3] = -1;
                location += PxFormat;
                site += 4;
            }
        }
        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        result.copyPixelsFromBuffer(ByteBuffer.wrap(data));
        return result;
    }

    /**
     * 获取指定像素数据_数组
     */
    public int[] GetPixel(int x, int y) {
        if (x >= 0 && x < Width && y >= 0 && y < Height) {
            int location = x * PxFormat + y * RowStride;
            int[] result = new int[3];
            result[0] = ScreenData[location] & 0xff;
            result[1] = ScreenData[location + 1] & 0xff;
            result[2] = ScreenData[location + 2] & 0xff;
            return result;
        } else {
            return new int[]{0, 0, 0};
        }
    }

    /**
     * 获取指定像素数据_string
     */
    public String GetPixelStr(int x, int y) {
        int[] result = GetPixel(x, y);
        int color = (result[0] & 0xff) << 16 | (result[1] & 0xff) << 8 | (result[2] & 0xff);
        return String.format("0x%06x", color);
    }

    /**
     * 获取指定像素数据_hex
     */
    public int GetPixelHex(int x, int y) {
        int[] result = GetPixel(x, y);
        return (result[0] & 0xff) << 16 | (result[1] & 0xff) << 8 | (result[2] & 0xff);
    }

    private boolean CompareColor(int[] description, int offsetX, int offsetY, int offsetLength) {
        int x = description[0] + offsetX;
        int y = description[1] + offsetY;
        int _x;
        int _y;

        Point[] offsetPoint = new Point[]{
                new Point(x, y),
                new Point(x - 1, y - 1),
                new Point(x - 1, y),
                new Point(x - 1, y + 1),
                new Point(x, y - 1),
                new Point(x, y + 1),
                new Point(x + 1, y - 1),
                new Point(x + 1, y),
                new Point(x + 1, y + 1),
        };

        for (int i = 0; i < offsetLength; i++) {
            _x = offsetPoint[i].x;
            _y = offsetPoint[i].y;
            if (_x >= 0 && _x < Width && _y >= 0 && _y < Height) {
                int location = _x * PxFormat + _y * RowStride;
                if (Abs((ScreenData[location] & 0xff) - description[2]) <= description[5]) {
                    if (Abs((ScreenData[location + 1] & 0xff) - description[3]) <= description[6]) {
                        if (Abs((ScreenData[location + 2] & 0xff) - description[4]) <= description[7]) {
                            return description[8] == 0;
                        }
                    }
                }
            }
        }
        return description[8] == 1;
    }

    public boolean CompareColor(int[] description, int sim, int offset) {
        int offsetLength = offset == 0 ? 1 : 9;
        int x = description[0];
        int y = description[1];

        int similarity = Round(255 - 255 * (sim / 100.0));
        int similarity_R = similarity + description[5];
        int similarity_G = similarity + description[6];
        int similarity_B = similarity + description[7];

        int[] temp = new int[]{
                description[0],
                description[1],
                description[2],
                description[3],
                description[4],
                similarity_R,
                similarity_G,
                similarity_B,
                description[8]
        };

        return CompareColor(temp, 0, 0, offsetLength);
    }

    private boolean CompareColorEx(int[][] description, int x, int y, int offsetLength) {
        for (int[] temp : description) {
            if (!CompareColor(temp, x, y, offsetLength)) {
                return false;
            }
        }
        return true;
    }

    public boolean CompareColorEx(int[][] description, int sim, int offset) {
        for (int[] temp : description) {
            if (!CompareColor(temp, sim, offset)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 条件循环多点比色
     *
     * @param description: 色组描述
     * @param sim:         相似度
     * @param offset:      偏移查找
     * @param timeout:     超时时间
     * @param timelag:     间隔时间
     * @param sign:        跳出条件,0为比色成功时返回,1为比色失败时返回
     */
    public boolean CompareColorExLoop(int[][] description, int sim, int offset, long timeout, long timelag, int sign) throws InterruptedException {
        int num = (int) (timeout / timelag);
        if (sign == 0) {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(false)) {
                    if (CompareColorEx(description, sim, offset)) {
                        return true;
                    }
                }
                Thread.sleep(timelag);
            }
        } else {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(false)) {
                    if (!CompareColorEx(description, sim, offset)) {
                        return true;
                    }
                }
                Thread.sleep(timelag);
            }
        }
        return false;
    }


    private Point FindMultiColor(int startX, int startY, int endX, int endY, int[] firstDescription, int[][] offsetDescription, int offsetLength) {
        int red = firstDescription[2];
        for (int i = Starts[red], end = Steps[red]; i < end; i += 2) {
            int x = RedList[i];
            int y = RedList[i + 1];
            if (x >= startX && x <= endX && y >= startY && y <= endY) {
                int location = x * PxFormat + y * RowStride;
                if (Abs((ScreenData[location + 1] & 0xff) - firstDescription[3]) <= firstDescription[6]) {
                    if (Abs((ScreenData[location + 2] & 0xff) - firstDescription[4]) <= firstDescription[7]) {
                        if (CompareColorEx(offsetDescription, x, y, offsetLength)) {
                            return new Point(x, y);
                        }
                    }
                }
            } else if (x > endX && y > endY) {
                break;
            }
        }
        return new Point(-1, -1);
    }

    public Point FindMultiColor(int startX, int startY, int endX, int endY, int[][] description, int sim, int offset) {
        startX = Math.max(startX, 0);
        startY = Math.max(startY, 0);
        endX = Math.min(endX, Width - 1);
        endY = Math.min(endY, Height - 1);
        int[] firstDescription = Arrays.copyOf(description[0], 9);
        int[][] offsetDescription = new int[description.length - 1][9];

        int similarity = Round(255 - 255 * (sim / 100.0));
        firstDescription[5] = similarity + description[0][5];
        firstDescription[6] = similarity + description[0][6];
        firstDescription[7] = similarity + description[0][7];

        for (int j = 0; j < offsetDescription.length; j++) {
            offsetDescription[j] = Arrays.copyOf(description[j + 1], 9);
            offsetDescription[j][5] = similarity + description[j + 1][5];
            offsetDescription[j][6] = similarity + description[j + 1][6];
            offsetDescription[j][7] = similarity + description[j + 1][7];
        }

        int offsetLength = offset == 0 ? 1 : 9;
        boolean step = true;
        for (int i = 0; i < firstDescription[5]; i++) {
            int num;
            if (step) {
                num = description[0][2] + i;
                if (i != 0) {
                    i--;
                    step = false;
                }
            } else {
                num = description[0][2] - i;
                step = true;
            }
            if (num < 256 && num > -1) {
                firstDescription[2] = num & 0xff;
                Point point = FindMultiColor(startX, startY, endX, endY, firstDescription, offsetDescription, offsetLength);
                if (point.x != -1) {
                    return point;
                }
            }

        }
        return new Point(-1, -1);
    }

    public Point FindMultiColor(int[] range, int[][] description, int sim, int offset) {
        return FindMultiColor(range[0], range[1], range[2], range[3], description, sim, offset);
    }

    /**
     * 条件循环多点找色
     *
     * @param description: 色组描述
     * @param sim:         相似度
     * @param offset:      偏移查找
     * @param timeout:     超时时间
     * @param timelag:     间隔时间
     * @param sign:        跳出条件,0为找色成功时返回,1为找色失败时返回
     */
    public Point FindMultiColorLoop(int startX, int startY, int endX, int endY, int[][] description, int sim, int offset, long timeout, long timelag, int sign) throws InterruptedException {
        int num = (int) (timeout / timelag);
        if (sign == 0) {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(true)) {
                    Point result = FindMultiColor(startX, startY, endX, endY, description, sim, offset);
                    if (result.x != -1) {
                        return result;
                    }
                }
                Thread.sleep(timelag);
            }
        } else {
            for (int i = 0; i < num; i++) {
                if (KeepScreen(true)) {
                    Point result = FindMultiColor(startX, startY, endX, endY, description, sim, offset);
                    if (result.x == -1) {
                        return result;
                    }
                }
                Thread.sleep(timelag);
            }
        }
        return FindMultiColor(startX, startY, endX, endY, description, sim, offset);
    }

    public Point FindMultiColorLoop(int[] range, int[][] description, int sim, int offset, long timeout, long timelag, int sign) throws InterruptedException {
        return FindMultiColorLoop(range[0], range[1], range[2], range[3], description, sim, offset, timeout, timelag, sign);
    }

    private List<Point> FindMultiColorEx(int startX, int startY, int endX, int endY, int[] firstDescription, int[][] offsetDescription) {
        int red = firstDescription[2];
        List<Point> result = new ArrayList<>();

        for (int i = Starts[red], end = Steps[red]; i < end; i += 2) {
            int x = RedList[i];
            int y = RedList[i + 1];
            if (x >= startX && x <= endX && y >= startY && y <= endY) {
                int location = x * PxFormat + y * RowStride;
                if (Abs((ScreenData[location + 1] & 0xff) - firstDescription[3]) <= firstDescription[6]) {
                    if (Abs((ScreenData[location + 2] & 0xff) - firstDescription[4]) <= firstDescription[7]) {
                        if (CompareColorEx(offsetDescription, x, y, 1)) {
                            result.add(new Point(x, y));
                        }
                    }
                }
            } else if (x > endX && y > endY) {
                break;
            }
        }
        return result;
    }

    public List<Point> FindMultiColorEx(int startX, int startY, int endX, int endY, int[][] description, int sim, int filterNum) {
        startX = Math.max(startX, 0);
        startY = Math.max(startY, 0);
        endX = Math.min(endX, Width - 1);
        endY = Math.min(endY, Height - 1);
        int[] firstDescription = Arrays.copyOf(description[0], 9);
        int[][] offsetDescription = new int[description.length - 1][9];

        int similarity = Round(255 - 255 * (sim / 100.0));
        firstDescription[5] = similarity + description[0][5];
        firstDescription[6] = similarity + description[0][6];
        firstDescription[7] = similarity + description[0][7];

        for (int j = 0; j < offsetDescription.length; j++) {
            offsetDescription[j] = Arrays.copyOf(description[j + 1], 9);
            offsetDescription[j][5] = similarity + description[j + 1][5];
            offsetDescription[j][6] = similarity + description[j + 1][6];
            offsetDescription[j][7] = similarity + description[j + 1][7];
        }

        List<Point> points = new ArrayList<>();

        boolean step = true;
        for (int i = 0; i < firstDescription[5]; i++) {
            int num;
            if (step) {
                num = description[0][2] + i;
                if (i != 0) {
                    i--;
                    step = false;
                }
            } else {
                num = description[0][2] - i;
                step = true;
            }
            if (num < 256 && num > -1) {
                firstDescription[2] = num & 0xff;
                List<Point> temp = FindMultiColorEx(startX, startY, endX, endY, firstDescription, offsetDescription);
                if (temp.size() > 0) {
                    points.addAll(temp);
                }
            }
        }

        List<Point> result = new ArrayList<>();

        int filterX = filterNum;
        int filterY = filterNum;
        if (filterNum == -1) {
            int left = 0;
            int top = 0;
            int right = filterNum;
            int bottom = filterNum;
            for (int i = 1; i < description.length; i++) {
                left = Math.min(left, description[i][0]);
                top = Math.min(top, description[i][1]);
                right = Math.max(right, description[i][0]);
                bottom = Math.max(bottom, description[i][1]);
            }
            filterX = right - left;
            filterY = bottom - top;
        }

        for (int i = 0, len = points.size(); i < len; i++) {
            Point point = points.get(i);
            boolean isOverlap = false;
            for (int j = 0, size = result.size(); j < size; j++) {
                Point temp = result.get(j);
                if (Abs(point.x - temp.x) <= filterX && Abs(point.y - temp.y) <= filterY) {
                    isOverlap = true;
                    break;
                }
            }
            if (!isOverlap) {
                result.add(point);
            }
        }
        return result;
    }

    public List<Point> FindMultiColorEx(int startX, int startY, int endX, int endY, int[][] description, int sim) {
        return FindMultiColorEx(startX, startY, endX, endY, description, sim, -1);
    }

    public List<Point> FindMultiColorEx(int[] range, int[][] description, int sim, int filterNum) {
        return FindMultiColorEx(range[0], range[1], range[2], range[3], description, sim, filterNum);
    }

    public List<Point> FindMultiColorEx(int[] range, int[][] description, int sim) {
        return FindMultiColorEx(range[0], range[1], range[2], range[3], description, sim, -1);
    }
}
